package com.bolt.admin.module.sys.service;

import com.bolt.admin.exception.BizExceptionEnum;
import com.bolt.admin.module.sys.dto.UserDTO;
import com.bolt.admin.module.sys.dto.converter.UserConverter;
import com.bolt.admin.module.sys.entity.*;
import com.bolt.admin.module.sys.repository.OrgRepository;
import com.bolt.admin.module.sys.repository.UserLogonLogRepository;
import com.bolt.admin.module.sys.repository.UserRepository;
import com.bolt.admin.properties.LoginProperties;
import com.bolt.admin.security.shiro.AuthContextHolder;
import com.bolt.admin.security.shiro.AuthUserDetails;
import com.bolt.admin.security.shiro.exception.AuthErrorException;
import com.bolt.admin.security.shiro.jwt.UserInfo;
import com.bolt.common.BoltConstants;
import com.bolt.common.collection.IteratorUtil;
import com.bolt.common.convert.Convert;
import com.bolt.common.lang.Assert;
import com.bolt.common.reflect.ObjectUtil;
import com.bolt.common.utils.StrUtil;
import com.bolt.common.utils.UidUtil;
import com.bolt.convention.data.PageInfo;
import com.bolt.convention.exception.ServiceException;
import com.bolt.convention.exception.ServiceValidException;
import com.bolt.support.base.service.BaseService;
import com.bolt.support.cache.redis.RedisManager;
import com.bolt.support.spring.jpa.filter.FilterBuilder;
import com.bolt.support.spring.jpa.jpql.Clauses;
import com.bolt.support.spring.jpa.jpql.Ops;
import com.bolt.support.spring.jpa.repository.BasicJpaRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.bolt.admin.security.shiro.PasswordUtil.entryptPassword;


/**
 * Auto Generate Javadoc
 *
 * @author 日期:2018-02-26 16:46:13
 *         用户信息表
 **/
@Service
@Transactional(readOnly = true, rollbackFor = Exception.class)
@RequiredArgsConstructor
public class UserService extends BaseService <UserEntity, String> {


    private final UserRepository userRepository;

    private final OrgRepository orgRepository;

    private final UserLogonLogRepository userLogonLogRepository;

    private final RoleService roleService;

    private final PrivilegeService privilegeService;

    private final RedisManager redisManager;

    private final LoginProperties loginProperties;


    @Override
    protected BasicJpaRepository <UserEntity, String> getRepository() {
        return userRepository;
    }


    public PageInfo <UserDTO> findPage(Pageable pageable, UserDTO userDTO) {
        return jpaQueryFactory.selectDistinct(UserDTO.class,
                Clauses.of(UserEntity::getId),
                Clauses.of(UserEntity::getUserName),
                Clauses.of(UserEntity::getRealName),
                Clauses.of(UserEntity::getMobile),
                Clauses.of(UserEntity::getEmail),
                Clauses.of(UserEntity::getNickName),
                Clauses.of(UserEntity::getOrgId),
                Clauses.of(UserEntity::getEnabled),
                Clauses.of(OrgEntity::getName).as("orgName")
        ).from(Clauses.of(UserEntity.class))
                .innerJoin(Clauses.of(OrgEntity.class))
                .on(Clauses.of(UserEntity::getOrgId), Clauses.of(OrgEntity::getId))
                .leftJoin(Clauses.of(UserR2Role.class))
                .on(Clauses.of(UserR2Role::getUserId), Clauses.of(UserEntity::getId))
                .and(Ops.EQ, Clauses.of(UserEntity::getOrgId))
                .and(Ops.EQ, Clauses.of(UserEntity::getUserName))
                .and(Ops.EQ, Clauses.of(UserR2Role::getRoleId))
                .buildCondition(userDTO)
                .fetchPage(pageable);
    }

    /**
     * 基于登录界面输入的单一username参数值进行转换处理查询用户对象
     *
     * @param userName
     * @return
     */
    public Optional <UserEntity> findByUserName(String userName) {
        return this.findOne((FilterBuilder <UserEntity> f) ->
                f.lambda().eq(UserEntity::getUserName, userName).build()
        );
    }


    /**
     * 保存后台操作用户
     *
     * @param userDTO
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public void saveOrUpdate(UserDTO userDTO) {

        OrgEntity orgEntity = orgRepository.findById(userDTO.getOrgId())
                .orElseThrow(() -> new ServiceException(BizExceptionEnum.DATA_NOT_FIND));

        UserEntity user = UserConverter.toEntity(userDTO);
        if (user.isNew()) {
            user.setOrgInfo(orgEntity);
            long count = userRepository.findCountByUserName(user.getUserName());
            Assert.isTrue(count > 0, "用户名已经存在,请重新输入!");
            user.setAuthSalt(UidUtil.UID());
            // 首次创建密码为登录ID(统一分配置)
            String password = encodeUserPasswd(user, BoltConstants.DEAULT_PASSWORD);
            user.setPassword(password);
            if (StrUtil.isBlank(user.getRealName())) {
                user.setRealName(user.getUserName());
            }
            setRoleIds(userDTO.getRoleIds(), user);
            save(user);

        } else {
            UserEntity entity = userRepository.findById(userDTO.getId())
                    .orElseThrow(() -> new ServiceException(BizExceptionEnum.DATA_NOT_FIND));
            UserConverter.toEntity(entity, userDTO);
            entity.setOrgInfo(orgEntity);
            setRoleIds(userDTO.getRoleIds(), entity);
            save(entity);
        }
    }

    private void setRoleIds(String roleIds, UserEntity entity) {
        List <UserR2Role> userR2Roles = entity.getUserR2Roles();
        //没有关联对象集合，清空关联集合
        if (StrUtil.isEmpty(roleIds) && IteratorUtil.isNotEmpty(userR2Roles)) {
            userR2Roles.clear();
        } else {
            List <String> ids = StrUtil.split(roleIds, ',', true, true);
            //有关联对象集合，如果原来没有则初始化创建，否则移除不存在关联
            if (IteratorUtil.isNotEmpty(userR2Roles)) {
                userR2Roles.removeIf(r2 -> !ids.stream().anyMatch(roleId -> roleId.equals(r2.getRole().getId())));
                ids.removeIf(roleId -> entity.getUserR2Roles().stream().anyMatch(r2 -> r2.getRole().getId().equals(roleId)));
            }
            List <UserR2Role> newRole = new ArrayList <>();
            ids.forEach(roleId -> {
                UserR2Role r2 = new UserR2Role();
                r2.setUser(entity);
                RoleEntity role = new RoleEntity();
                role.setId(roleId);
                r2.setRole(role);
                newRole.add(r2);
            });
            if (ObjectUtil.isNotNull(userR2Roles)) {
                userR2Roles.addAll(newRole);
            } else {
                entity.setUserR2Roles(newRole);
            }
            redisManager.del(entity.getId());
        }
    }

    /**
     * 保存关存安角色
     *
     * @param entity
     * @param roleIds
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateRelatedRoleR2s(UserEntity entity, String[] roleIds) {
        updateRelatedR2s(entity, roleIds, "userR2Roles", "role");
    }


    /**
     * 处理密码加密
     *
     * @param user
     * @param rawPassword
     * @return
     */
    public String encodeUserPasswd(UserEntity user, String rawPassword) {
        return entryptPassword(rawPassword, user.getAuthSalt());
    }


    /**
     * 用户修改密码
     *
     * @param oldPassword
     * @param newPassword
     */
    @Transactional(rollbackFor = Exception.class)
    public void modifyPassword(String oldPassword, String newPassword) {

        AuthUserDetails userDetails = AuthContextHolder.getAuthUserDetails();
        UserEntity user = findOne(userDetails.getAccountId()).orElseThrow(() ->
                new AuthErrorException(BizExceptionEnum.USER_NOT_EXISTED));

        oldPassword = encodeUserPasswd(user, oldPassword);
        if (!oldPassword.equals(user.getPassword())) {
            throw new ServiceValidException("原始密码不正确");
        }
        newPassword = encodeUserPasswd(user, newPassword);
        user.setPassword(newPassword);
        this.save(user);
    }

    /**
     * 重置用密码
     * @param userId
     */
    @Transactional(rollbackFor = Exception.class)
    public void resetUserPassWord(String userId) {
        UserEntity user = findOne(userId).orElseThrow(() ->
                new AuthErrorException(BizExceptionEnum.USER_NOT_EXISTED));
        String encodedPassword = encodeUserPasswd(user, BoltConstants.DEAULT_PASSWORD);
        user.setPassword(encodedPassword);
        this.userRepository.save(user);
    }

    /**
     * 保存登录日志
     *
     * @param logonLog
     */
    @Transactional(rollbackFor = Exception.class)
    public void saveLoginLog(LoginLogEntity logonLog) {
        userLogonLogRepository.save(logonLog);
    }


    public UserInfo userInfo(String userId) {
        UserInfo userInfo = redisManager.getOrCache(userId, UserInfo.class, () -> {
            UserEntity account = findOne(userId).orElseThrow(() -> new ServiceException(BizExceptionEnum.DATA_NOT_FIND));
            UserInfo info = new UserInfo();
            info.setUserName(account.getUserName());
            info.setRealName(account.getRealName());
            info.setUserId(account.getId());
            OrgEntity org = account.getOrgInfo();
            info.setAreaCode(org.getAreaCode());
            info.setOrgCode(org.getCode());
            info.setOrgName(org.getName());
            info.setOrgId(org.getId());
            Set <String> roles = new LinkedHashSet <>();
            Set <String> stringPermissions = new LinkedHashSet <>();
            if (null != account) {
                //查询用户角色列表
                List <RoleEntity> userRoleDOS = roleService.findRoles(account);
                for (RoleEntity roleDO : userRoleDOS) {
                    roles.add(roleDO.getCode());
                }
                //超级管理员特殊处理
                if (account.getAdmin()) {
                    //追加超级权限配置
                    stringPermissions.add("*");
                }
                //基于当前用户所有角色集合获取有效的权限集合
                if (IteratorUtil.isEmpty(stringPermissions)) {
                    List <PrivilegeEntity> privileges = privilegeService.findPrivileges(roles);
                    for (PrivilegeEntity privilegeEntity : privileges) {
                        stringPermissions.add(privilegeEntity.getCode());
                    }
                }

            }

            info.setPermissions(stringPermissions);
            info.setRoles(roles);
            return info;
        }, RedisManager.ONE_HOUR * 2);
        return userInfo;
    }


    private static String LOGON_USER_LOCK = "user_lock_key";

    /**
     * 处理尝试登录失败用户锁定功能
     *
     * @param userName
     */
    public int setfailureLimit(String userName) {
        int failure = Convert.toInt(redisManager.get(userName), 0);
        if (failure > loginProperties.getLogonFailureLimit()) {
            redisManager.cache(LOGON_USER_LOCK + userName, "true", loginProperties.getLockedTime(), TimeUnit.MINUTES);
        } else {
            failure = failure + 1;
            redisManager.cache(userName, String.valueOf(failure), 1, TimeUnit.MINUTES);
        }
        return failure;
    }

    public boolean isUserLock(String userName) {
        return Convert.toBool(redisManager.get(LOGON_USER_LOCK + userName), false);
    }



}
